package tool;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import tool.analysis.InterfaceUseAnalyser;
import tool.analysis.InterfaceUseAsTypeAnalyser;
import tool.analysis.series.ISeriesMember;
import tool.codescan.TypeResolver;
import tool.codescan.loggers.InterfaceAndClassLogger;
import tool.codescan.loggers.VariableAndParameterLogger;
import tool.codescan.mappers.PackageMapper;
import tool.codescan.visitors.JavaFileVisitor;
import tool.codescan.visitors.PreVisitSetupVisitor;
import tool.csv.CSVCompatible;
import tool.csv.FileWriter;
import tool.data.ResultStore;
import japa.parser.JavaParser;
import japa.parser.ParseException;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.Node;
import japa.parser.ast.visitor.VoidVisitor;

	/**
	 * @author jwb09119
	 * @date 2013/11/13
	 * 
	 * This class is responsible for running experiments.  There is only one traversal by one
	 * visitor at the moment, but there are likely to be more soon.  This class ensures running 
	 * order.
	 *
	 */

public class Experimenter implements ISeriesMember{

    private String target;
	private List<CSVCompatible> outputGenerators;
	private ResultStore results;
	private PackageMapper packageResolver;
	private TypeResolver typeResolver;
	private InterfaceAndClassLogger typeLogger;
	private VariableAndParameterLogger useLogger;
	private FileWriter writer;
	
	private InterfaceUseAnalyser basicUseInspector;
	InterfaceUseAsTypeAnalyser useAsTypeInspector;
	
	
	public Experimenter (File srcDir, List<File> dependencies) {
		
	    target = srcDir.getAbsolutePath();
		outputGenerators = new ArrayList<>();
		
		writer = new FileWriter (srcDir.getParentFile().toPath());
		results = new ResultStore(writer);
		outputGenerators.add(results);
		typeLogger = new InterfaceAndClassLogger(results);
		useLogger = new VariableAndParameterLogger(results);
		
		runExperiments(srcDir, dependencies);
		
	}
	
	
	/**
	 * Runs all defined analysers on the specified source code
	 * @param javaFileRootDirectory - the location of the source code to analyse
	 * @param dependencies - the location(s) of dependencies for the source code in the location
	 *                       specified by javaFileRootDirectory
	 */
	private void runExperiments (File javaFileRootDirectory, List<File> dependencies) {
		
		// SET UP
		System.out.println("Mapping source packages...");
		packageResolver = new PackageMapper(javaFileRootDirectory);
		
		System.out.println("Loading implicit Java imports...");
		typeResolver = new TypeResolver(packageResolver, dependencies, results);

		// PARSERS
		System.out.print("Doing pre-parse traversal...");
		PreVisitSetupVisitor<Node> preParser = new PreVisitSetupVisitor<Node>(results);
		doTraversal(preParser, javaFileRootDirectory);
		System.out.println(" done");
		
		System.out.print("Parsing files...");
		VoidVisitor<Node> iVisitor = new JavaFileVisitor<Node>(typeLogger, useLogger, typeResolver);
		doTraversal(iVisitor, javaFileRootDirectory);
		System.out.println(" done");
		
		// ANALYSERS
		System.out.println("Starting analysis...");
		
		basicUseInspector = new InterfaceUseAnalyser(writer, results);
		outputGenerators.add(basicUseInspector);
		useAsTypeInspector = new InterfaceUseAsTypeAnalyser(writer, results);
		outputGenerators.add(useAsTypeInspector);

		// OUTPUT
		System.out.print("Writing data to output files...");
		
		for (CSVCompatible csv : outputGenerators) {
			
			csv.exportResults(javaFileRootDirectory.getAbsolutePath().toString());
			System.out.print(".");
		}
		
		System.out.println(" done");
		
	}
	
	
	/**
	 * Visits each compile-able java file in the location represented by rootDirectory with the 
	 * visitor object v
	 * @param v - a visitor object that can visit Node types
	 * @param rootDirectory - File - a location
	 * @return int - the number of java files found that the visitor was offered to.
	 */
	private void doTraversal (VoidVisitor<Node> v, File rootDirectory) {
		
		assert(rootDirectory.isDirectory()) : "Experimenter: Traversal target must be a directory";
		
		List<File> files = listJavaFiles(rootDirectory);
		
		CompilationUnit unit = null;
		
		int counter = 0;
		
		for(File thisFile : files) {
		
			if (0 == (counter % 100)) { System.out.print("."); };
			
			try (FileInputStream in = new FileInputStream(thisFile)){
				
				unit = JavaParser.parse(in);
				
			} catch (ParseException | IOException e) {
				
				System.out.println("\nProblem parsing file '"+thisFile.getName()+"' at location '"+thisFile.getAbsolutePath()+"'\n");
				e.printStackTrace();
				
			}
			
			unit.accept(v, unit);
			counter++;
			
		}
		
	}
	
	
	/**
	 * Gets all the .java files in a target location (including sub-folders)
	 * @param location
	 * @return a List of File who's names end in .java
	 */
	private List<File> listJavaFiles (File location) {
		
		List<File> reply = new ArrayList<File>();
		
		if (location.isFile() && location.getName().endsWith(".java")) {
			
			reply.add(location);
			
		} else {
			
			// Recursively handle sub-directories
			if (location.isDirectory()) {
				
				File[] nextFiles = location.listFiles();
				
				if (nextFiles != null) {
					
					for (File newFile : nextFiles) {
						
						reply.addAll(listJavaFiles(newFile));
						
					}
					
				}
				
			}
			
		}
		
		return reply;
		
	}

	
	/*************************
	 * Series Member Methods *
	 *************************/
    @Override
    public String getSubjectName () {

        return target;
        
    }


    @Override
    public int getConcreteClassCount () {

        return results.getConcreteClassCount();
        
    }


    @Override
    public int getAbstractClassCount () {

        return results.getAbstractClassCount();
        
    }


    @Override
    public int getInterfaceCount () {

        return results.getInterfaceCount();
        
    }


    @Override
    public int getCountofEntitiesImplementingInterfaces () {

        return results.getCountOfAbstractClassImplements() + 
                results.getCountOfConcreteClassImplements();

    }


    @Override
    public int getCountOfEntitiesExtendingInterfaces () {

        return results.getCountOfInterfaceExtends();
        
    }


    @Override
    public int getCountOfEntities () {

        return results.getInterfaceCount() + 
               results.getAbstractClassCount() + 
               results.getConcreteClassCount();
        
    }

    @Override
    public Set<String> getInterfaceNames () {
        
        Set<String> reply = results.getAllInterfaceNames();
        
        reply.addAll(basicUseInspector.getAllInterfaceNames());
                
        return reply;
        
    }


    @Override
    public int getImplementersCountFor (String interfaceName) {

        int reply = -1;
        
        if (results.interfaceExists(interfaceName)) {
        
            reply = results.getImplementersCountFor(interfaceName);
        
        }
        
        return reply;
        
    }


    @Override
    public int getExtendersCountFor (String interfaceName) {

        int reply = -1;

        if (results.interfaceExists(interfaceName)) {
        
            reply = results.getExtendersCountFor(interfaceName);
            
        }
        
        return reply;
    }


    @Override
    public int getUseAsClassVariableCount (String interfaceName) {

        int reply = -1;
        
        if (useAsTypeInspector.containsInterface(interfaceName)) {
            
            reply = useAsTypeInspector.getClassVarCountForInterface(interfaceName);
            
        }
        
        return reply;
    }


    @Override
    public int getUseAsMethodParameterCount (String interfaceName) {

        int reply = -1;
        
        if (useAsTypeInspector.containsInterface(interfaceName)) {
            
            reply = useAsTypeInspector.getMethodParamCountForInterface(interfaceName);
            
        }
        
        return reply;
        
    }


    @Override
    public int getUseAsLocalVariableCount (String interfaceName) {

        int reply = -1;
        
        if (useAsTypeInspector.containsInterface(interfaceName)) {
            
            reply = useAsTypeInspector.getLocalVarCountForInterface(interfaceName);
            
        }
        
        return reply;
        
    }
    
    
}

